/**
 * 
 */
package gen;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.exceptions.PersistenceException;
import org.apache.ibatis.session.SqlSession;
import constrain.Constraint;
import constrain.ConstraintType;
import constrain.TableLink;
import sources.ISource;
import sources.IWrapper;

/**
 * @author Kyniek
 *
 * Tabela posiada właściwości :
 * - size : rozmiar, wiel ile ma mieć wierszy
 * - name : wie jak się nazywa, jest to nazwa w docelowej bazie danych, nazwa musi być unikalna
 * - rowGen : RowGenerator - określa w jaki sposób mają być tworzone nowe wiersze
 * 
 * - links : tabela wie o więzach spójności, relacjach z innymi tabelami. Jednakże logika obsługiwana jest przez schemat.
 * 
 * 
 * Tabela jest składnikiem Schematu, Schemat określa powiązania między tabelami.
 * 
 * 
 * Tabela musi mieć metody do generowania nowych tabel :
 *  1) wypełnianie kolumn źródłami pierwotnymi - rowGenerator
 *    - sugestia : możliwe zrezygnowanie z generatora wierszy
 *  2) uzupełnianie relacji PK -> FK - Tabela/Schemat
 *  3) uzupełnianie pozostałych kolumn typu post-process - Tabela/Schemat 
 *
 *
 *  TODO :
 *  - metoda generująca kody selectów - specyficzne selecty są niezbedne do odzyskiwania rezultatu w postaci hashmapy.
 *  
 */
/**
 * @author Kyniek
 *
 */
public class Table
{
	public enum Mode {OVERWRITE, APPEND};
	
	//nazwa tabeli
	private String name;
	
	//rozmiar tabeli
	int size;
	
	//źródła danych
	List<ISource> sources;
	
	//całkiem możliwe że nie będzie potrzebne
	List<TableLink> links;
	
	//mapuje nazwę źródła/kolumny na źródło
	Map<String, ISource> name2source;
		
	/**
	 * tryb działania - czy nadpisujemy istniejącą (lub nieistniejącą) tabelę czy dołączamy wiersze do istniejącej
	 */
	Mode mode = Mode.OVERWRITE;
	
	
	/**
	 * Dane są przechowywane są jako mapa list, 
	 */
	Map<String, List<Object>> data;
	
	
	//ID do zdefiniowanej w mapper.xml operacji wstawiania - jest to konieczne by wykorzystać obsługę typów w mybatisie
	String insertId = "";
	
	String updateId = "";
	
	String selectID = "";
		
	
	Schema schema;
	
	//tabela powinna znać swój klucz główny
	ISource PK;
	
	//tabela powinna wiedzieć który constraint dotyczy klucza głównego
	Constraint PKconstraint;	
	
	public Table()
	{
		this("imie", 10, null);
		name = "def...";		
		System.out.println("def...");
	}	
	
	public Table(String nam, int siz,final List<ISource> srcs,final List<IWrapper> wraps)
	{
		this(nam, siz, (new IWrapper() {
			
			@Override
			public List<ISource> getAll()
			{
				List<ISource> ls = new ArrayList<ISource>();
				ls.addAll(srcs);
				
				for(IWrapper wra : wraps)
				{
					ls.addAll(wra.getAll());
				}				
				return ls;
			}
		}).getAll() );
	}
	
	
	public Table(String nam, int siz, List<ISource> srcs)
	{
		//System.out.println("Table.Table() : " + nam);
		name = nam;
		//rowGen = rowg;
		size = siz;
		
		sources = srcs;
		//List<String> ls = new ArrayList<String>();
		//String[] tab = new String[]{"s", "t", "r"};
		//ls.addAll(  Lists.newArrayList(tab) );
		name2source = new HashMap<String, ISource>();
		boolean hasDeclaredPK = false;
		for(ISource src : srcs)
		{
			name2source.put(src.getName(), src);			
			
			if(src.getConstraint() != null && src.getConstraint().size() > 0)
			{
				for(Constraint con : src.getConstraint())
				{
					if(con.getType() == ConstraintType.PRIMARY_KEY)
					{
						PK = src;
						PKconstraint = con;
						hasDeclaredPK = true;
					}
				}
			}
		}
		
		//TODO : jeśli nie zadeklarowano PK jawnie to należy poszukać kolumny nazywającej się ID/id i ją ustanowić PK
		if(!hasDeclaredPK)
		{
			for(ISource src : srcs)
			{
				if(src.getName().toLowerCase().equals("id"))
				{
					Constraint cs = new Constraint(ConstraintType.PRIMARY_KEY);
					if(src.getConstraint() != null && src.getConstraint().size() > 0)
					{						
						src.getConstraint().add(cs);
					}
					else
					{
						List<Constraint> tmpLs = new ArrayList<Constraint>();
						tmpLs.add(cs);
						src.setConstraint(tmpLs);
					}
				}
			}
		}		
	}

	
	/**
	 * Pierwszy etap to stworzenie tabeli z : kluczem głównym i powiązaniami (np FK->PK lub not null)
	 */
	public void createTable()
	{		
		String columns = "";
		
		for(ISource src : sources)
		{	
			columns += src.getName() + " " + src.getDataType();
			if(src.getConstraint() != null && src.getConstraint().size() > 0)
			{
				for(Constraint cs : src.getConstraint())
				{
					System.out.println("code : " + cs.getCode() );
					columns += cs.getCode();
				}
			}
			columns += ",";
		}
		columns = columns.substring(0, columns.length() - 1);
		
		HashMap<String, String> pars = new HashMap<String, String>();
		pars.put("tab", name);
		pars.put("cols", columns);
		
		System.out.println("with columns : \n -" + columns);

		//System.out.println("creating table " + name + "...");
		
		SqlSession session = GeneratorMain.sessionFactory();		
		if(mode == Mode.OVERWRITE)
		{
			try
			{
				session.update("dropTab", pars);
				session.commit();
			}
			catch(PersistenceException e)
			{
				System.err.println(e.getMessage());
				System.err.println("Tabela " + name + " nie istnieje, więc nie może zostać usunięta..." );
			}
			session.update("createTab", pars);
		}
		else
		{
			session.update("createTab", pars);
		}		
		session.commit();
		session.close();
	}
	
	/**
	 * generowanie kodu dla INSERT, który zostanie umieszczony w mapper.xml
	 * 
	 * status : OK
	 * @return
	 */
	protected String generateInsertCode()
	{
		String code = "";
		String insId = getName() + "_ins";
		code += "<insert id=\"" + insId + "\">\n";
		setInsertId(insId);
		
		String begIns = "INSERT INTO " + getName() + " (";
		String endIns = "VALUES (";
		
		for(ISource src : getSources())
		{				
			if(src.getConstraint() != null && src.getConstraint().size() > 0)
			{
				boolean omit = false;
				for(Constraint cons : src.getConstraint())
				{
					if(cons.getType() == ConstraintType.PRIMARY_KEY && cons.getCode() != null && cons.getCode().contains("AUTOINCREMENT"))
					{
						omit = true;
					}
				}
				if(!omit)
				{
					begIns += src.getName() + ",";
					endIns += "#{" + src.getName() + ", typeHandler=handler.kynObjectTypeHandler},";						
				}
			}
			else
			{
				begIns += src.getName() + ",";
				endIns += "#{" + src.getName() + ", typeHandler=handler.kynObjectTypeHandler},";
			}
		}
		
		begIns = begIns.substring(0, begIns.length() - 1);
		begIns += ") ";
		endIns = endIns.substring(0, endIns.length() - 1);
		endIns += ") ";
		
		code += begIns;
		code += endIns;
		code += "\n</insert>\n\n";		
			
		return code;
	}
	
	/**
	 * Metoda generująca kod selecta, dzięki temu każda taela wie jak wybrać dane z bazy. 
	 * Prawdopodobnie na dalszym etapie nie będzie potrzebna
	 * 
	 * status : nie wiadomo czy potrzebne
	 * @return
	 */
	protected String generateSelectCode()
	{
		selectID = getName() + "_sel";
		String code = "<select id=\"" + selectID + "\" resultType=\"hashMap\">\nSELECT ";
				//select surname from  ${value}
		for(ISource src : sources)
		{
			code += src.getName() + ",";
		}
		code = code.substring(0, code.length() - 1);

		code += " FROM " + getName() + "\n</select>\n\n";
		return code;
	}
	
	
	/**
	 * Generuje liczbę wierszy określoną w rozmiarze - size
	 * 
	 * status : OK
	 */
	public void generateRows()
	{
		System.out.println("generating primary rows...");
		Map<String, Object> tmpMap = new HashMap<String, Object>();
		SqlSession session = GeneratorMain.sessionFactory();		
		//System.out.println("");
		for(int i = 0; i < size; i++)
		{
			//wygenerowanie pojedynczego wiersza
			//do rozważenia tworzenie pakietów wierszy np po 10 czy 100
			for(ISource src : sources)
			{
				//System.out.print( src.get() + ", " );
				tmpMap.put(src.getName(), src.get() );
			}
			//System.out.println("");
			session.insert(insertId, tmpMap);			
		}		
		session.commit();		
		session.close();
		
	}
	
	/**
	 * uzupełnia tabelę o klucze obce. Dzieje się to w drugim przebiegu, po wygenerowaniu i zapisaniu 
	 * danych pierwotnych do tabeli.
	 */
	public void populateForeignKeys()
	{
		//algorytm najpierw wyszukuje źródła oznaczone jako FK
		
		for(ISource src : sources)
		{
			if(src.getConstraint() != null && src.getConstraint().size() > 0)
			{
				for(Constraint con : src.getConstraint())
				{
					if(con.getType() == ConstraintType.FOREIGN_KEY)
					{
						System.out.println("...generating FOREIGN KEY....");
						SqlSession session = GeneratorMain.sessionFactory();
						con.executeConstrint(this, src, session);
						session.commit();
						session.close();
					}
				}
			}
		}
		
		SqlSession session = GeneratorMain.sessionFactory();
		
//		List<Object> pks = selIDS(session);
//		for(Object ob : pks)
//		{
//			System.out.println("ID : " + ob + " : " + ob.getClass().getCanonicalName());
//		}
		
		
		System.out.println("populating foreign keys...");
	}
	
	public void postProcessPopulation()
	{
		System.out.println("postProcess population...");
	}
	
	
	/**
	 * Wybiera wszystkie wartości klucza głównego i zwraca listę
	 * @param session
	 * @return
	 */
	public List<Object> selIDS(SqlSession session)
	{
		return  session.selectList(GeneratorMain.selSingle  , "select " + PK.getName() + " from " + getName());
	}
	
	/**
	 * aktualizuje daną kolumnę wartościami podanymi w liście, jeśli lista jest za krótka w stosunku do liczby rekordów w kolumnie aktualizowanej
	 * jest ta lista odczytywana cyklicznie
	 * @param trg kolumna reprezentowana przez źródło, która ma być zaaktualizowana
	 * @param vals lista wartości
	 * @param session sesja do bazy danych
	 */
	public void batchUpdate(ISource trg, List vals, SqlSession session)
	{
		List ids = this.selIDS(session);
		HashMap<String, Object> params = new HashMap<String, Object>();
		int licz = 0;
		for(Object obID : ids)
		{
			//update ${tab} SET ${col} = #{val} WHERE {col2} = #{val2}
			params.put("tab", this.name);
			params.put("col", trg.getName());
			params.put("val", vals.get(licz%vals.size()));
			params.put("col2", this.PK.getName());
			params.put("val2", obID);
			session.update("update_where", params);
			
			licz++;
		}
	}
	
	
	public String getName()
	{		
		return name;
	}

	public void setName(String name)
	{
		System.out.println("getting name...");
		this.name = name;
	}


	public List<TableLink> getLinks()
	{
		return links;
	}


	public void setLinks(List<TableLink> links)
	{
		this.links = links;
	}


	public Mode getMode()
	{
		return mode;
	}


	public void setMode(Mode mode)
	{
		this.mode = mode;
	}


	public String getInsertId()
	{
		return insertId;
	}


	public void setInsertId(String insertId)
	{
		this.insertId = insertId;
	}


	public List<ISource> getSources()
	{
		return sources;
	}


	public void setSources(List<ISource> sources)
	{
		this.sources = sources;
	}


	public Schema getSchema()
	{
		return schema;
	}


	public void setSchema(Schema schema)
	{
		this.schema = schema;
	}


	public ISource getPK()
	{
		return PK;
	}


	public int getSize()
	{
		return size;
	}

}